package com.log.service;

import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.Validate;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;

import com.log.annotation.LogCollection;
import com.log.template.BaseLogBean;
import com.log.template.LocalLoggerType;
import com.log.template.LogCollectionType;
import com.log.template.LogPkg;

/**
 * 
 * @author ZGame 初级版本
 * 数据库日志服务类
 * 分布式日志记录,不同的服务器会把日志记录在一起
 * 主要记录需要统计或者非常重要的日志
 * 记录了数据库日志,必须再次记录到本地日志当中,已做备份记录
 */
@Service
public class GameLogService {
	public static final Logger LOGGER = LoggerFactory.getLogger(GameLogService.class);//
	@Autowired
	private MongoTemplate logMongoTemplate;// 连接对象
	@Autowired
	private LogPkg logPkg;

	private ThreadPoolExecutor defaultExecutor;
	private ThreadPoolExecutor handlerExecutor;
	private Timer timer = new Timer("GameLogService-UpdateTimer");
	private BlockingQueue<Runnable> workerQueue = new LinkedBlockingQueue<Runnable>();
	private BlockingQueue<Runnable> handlerQueue = new LinkedBlockingQueue<Runnable>();
	private Map<String, LogCollectionType> collectionTypeMap = new HashMap<>();
	private ClassPathXmlApplicationContext context;
	private List<BaseLogBean> logs = new ArrayList<BaseLogBean>();
	private Object staticHelper = new Object();
	private boolean stopped;
	private long totalRecord = 0;
	private long lastCheckTime;

	protected static final SimpleDateFormat YYYY_MM_DD = new SimpleDateFormat("yyyyMMdd");
	protected static final SimpleDateFormat YYYY_MM_WW = new SimpleDateFormat("yyyyMMWW");
	protected static final SimpleDateFormat YYYY_MM = new SimpleDateFormat("yyyyMM");

	public void start() {
		Reflections refs = new Reflections(logPkg.getScanPkg(), new SubTypesScanner(false));
		for (Class<? extends BaseLogBean> clazz : refs.getSubTypesOf(BaseLogBean.class)) {
			if (Modifier.isAbstract(clazz.getModifiers())) {
				continue;
			}
			LogCollection table = Validate.notNull(clazz.getAnnotation(LogCollection.class),
					String.format("%s 必须继承 %s。", clazz.getName(), LogCollection.class.getName()));
			collectionTypeMap.put(clazz.getSimpleName().toLowerCase(), table.collectionType());
		}
		defaultExecutor = new ThreadPoolExecutor(1, logPkg.getMaximumPoolSize(), 0, TimeUnit.MILLISECONDS, workerQueue,
				r -> {
					Thread thread = new Thread(r);
					thread.setName("GameLogService-Default");
					thread.setUncaughtExceptionHandler((t, e) -> {
						LOGGER.error("捕获未处理的异常！", e);
					});
					return thread;
				});
		handlerExecutor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, handlerQueue, r -> {
			Thread thread = new Thread(r);
			thread.setName("GameLogServer-Handler");
			thread.setUncaughtExceptionHandler((t, e) -> {
				LOGGER.error("捕获未处理的异常！", e);
			});
			return thread;
		});
	}

	public void stop() {
		synchronized (staticHelper) {
			stopped = true;
		}
		if (timer != null) {
			timer.cancel();
		}
		while (!logs.isEmpty()) {
			LOGGER.info("尚有'{}'条日志未保存！" + logs.size());
		}
		context.close();
	}

	public void dbExcute(BaseLogBean log) {
		try {
			defaultExecutor.execute(() -> {
				//数据库记录
				logMongoTemplate.save(log, buildCollectionName(log));
				//本地记录
				LocalLoggerType.registLog.getLoger().info(log.toJson());
			});
		} catch (Exception e) {
			LOGGER.error("日志服务器捕获到未处理的异常！", e);
		}
	}
	
	protected String buildCollectionName(BaseLogBean log) {
		String name = log.getClass().getSimpleName().toLowerCase();
		long time = log.getDealTime() * 1000l;
		LogCollectionType type = collectionTypeMap.get(name);
		switch (type) {
		case Day: {
			return String.format("%s_%s_%s", name, type.getType(), YYYY_MM_DD.format(new Date(time)));
		}
		case WEEK: {
			return String.format("%s_%s_%s", name, type.getType(), YYYY_MM_WW.format(new Date(time)));
		}
		case Month: {
			return String.format("%s_%s_%s", name, type.getType(), YYYY_MM.format(new Date(time)));
		}
		default: {
			return String.format("%s_%s", name, type.name().toLowerCase());
		}
		}
	}

	public boolean isSameDay(long time, long time2) {
		Calendar instance = Calendar.getInstance();
		instance.setTimeInMillis(time);
		int d1 = instance.get(Calendar.DAY_OF_YEAR);
		int y1 = instance.get(Calendar.YEAR);
		instance.setTimeInMillis(time2);
		int d2 = instance.get(Calendar.DAY_OF_YEAR);
		int y2 = instance.get(Calendar.YEAR);
		return y1 == y2 && d1 == d2;
	}

	public int duringDay(long time, long time2) {
		Calendar instance = Calendar.getInstance();
		instance.setTimeInMillis(time);
		int d1 = instance.get(Calendar.DAY_OF_YEAR);
		int y1 = instance.get(Calendar.YEAR);
		instance.setTimeInMillis(time2);
		int d2 = instance.get(Calendar.DAY_OF_YEAR);
		int y2 = instance.get(Calendar.YEAR);
		int duringYear = Math.abs(y1 - y2);
		int duringDay = Math.abs(d1 - d2);
		if (duringYear == 0) {
			return duringDay;
		} else if (duringYear >= 1) {
			if (y1 > y2) {
				return y1 + 365 + d1 - d2;
			} else {
				return y2 + 365 + d2 - d1;
			}
		}
		return 0;
	}

	public int duringMonth(long time, long time2) {
		Calendar instance = Calendar.getInstance();
		instance.setTimeInMillis(time);
		int m1 = instance.get(Calendar.MONTH);
		int y1 = instance.get(Calendar.YEAR);
		instance.setTimeInMillis(time2);
		int m2 = instance.get(Calendar.MONTH);
		int y2 = instance.get(Calendar.YEAR);
		int duringYear = Math.abs(y1 - y2);
		int duringMonth = Math.abs(m1 - m2);
		if (duringYear == 0) {
			return duringMonth;
		} else if (duringYear >= 1) {
			if (y1 > y2) {
				return y1 + 12 + m1 - m2;
			} else {
				return y2 + 12 + m2 - m1;
			}
		}
		return 0;
	}

	public int duringWeek(long time, long time2) {
		Calendar instance = Calendar.getInstance();
		instance.setTimeInMillis(time);
		int w1 = instance.get(Calendar.WEEK_OF_YEAR);
		int y1 = instance.get(Calendar.YEAR);
		instance.setTimeInMillis(time2);
		int w2 = instance.get(Calendar.WEEK_OF_YEAR);
		int y2 = instance.get(Calendar.YEAR);
		int duringYear = Math.abs(y1 - y2);
		int duringWeek = Math.abs(w1 - w2);
		if (duringYear == 0) {
			return duringWeek;
		} else if (duringYear >= 1) {
			if (y1 > y2) {
				return y1 + 52 + w1 - w2;
			} else {
				return y2 + 52 + w2 - w1;
			}
		}
		return 0;
	}
}
